package cn.org.itool.ws.framework.ws.group;

import cn.org.itool.common.constent.WsCodeEnum;
import cn.org.itool.ws.framework.netty.auth.Principal;
import cn.org.itool.ws.framework.utils.ChannelUtils;
import cn.org.itool.ws.pojo.UserTag;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.log4j.Log4j2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

@Log4j2
public class WsDefaultChannelGroup extends DefaultChannelGroup implements WsChannelGroup {

    private static final Logger LOGGER = LoggerFactory.getLogger(WsDefaultChannelGroup.class);
    /**
     * 用户关联的id -> channel
     */
    private final ConcurrentMap<String, Channel> principalChannels =new ConcurrentHashMap<>();

    private final ChannelFutureListener remover = f -> removeFromChannels(f.channel());


    public WsDefaultChannelGroup(EventExecutor executor) {
        super(executor);
    }

    public WsDefaultChannelGroup(String groupName) {
        super(groupName, GlobalEventExecutor.INSTANCE);
    }

    /**
     * 在线用户数
     * @return 在线数
     */
    public int onlineCount() {
        return principalChannels.values().size();
    }

    @Override
    public boolean put(Channel channel, Principal principal, List<Channel> oChannels){
        boolean success = super.add(channel);
        if(!success){
            LOGGER.error("Put channel to group fail channelId={}, priId={}", channel.id().asLongText(), principal.getId());
            return false;
        }

        channel.closeFuture().addListener(remover);
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("Put channel principalId={}, channelId={}", principal.getId(), channel.id().asLongText());
        }
        Channel o = principalChannels.put(principal.getId(), channel);
        if(o != null){
            oChannels.add(o);
        }

        return true;
    }

    @Override
    public Channel findByPriId(String priId){
        return principalChannels.get(priId);
    }

    @Override
    public Set<String> priIdAll(){
        return principalChannels.keySet();
    }

    @Override
    public Set<UserTag> userTagAll(){
        return principalChannels.values().stream().map(ChannelUtils::getUserTag).collect(Collectors.toSet());
    }


    /**
     * 移除{@code principalChannels}中的数据
     *
     * @param channel {@link Channel}
     */
    private void removeFromChannels(Channel channel){
        Principal principal = ChannelUtils.getPrincipal(channel);
        boolean ok = principalChannels.remove(principal.getId(), channel);
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("Ws group remove channel principalId={}, channelId={}, deleted={}",
                    principal.getId(), channel.id().asLongText(), ok);
        }
        channel.closeFuture().removeListener(remover);
    }

    @Override
    public boolean equals(Object o) {
        return this == o;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this);
    }

    public void closeAll() {
        for (Channel value : principalChannels.values()) {
            try {
                log.info("-close channel:{}", value.id().asLongText());
                value.close().sync();
            }catch (Exception e){
                log.error("e:", e);
            }
        }
    }

    public void classByWsIds(Set<String> wsIds) {
        for (Channel value : principalChannels.values()) {
            try {
                String wsId = value.id().asLongText();
                if(wsIds.contains(wsId)) {
                    log.info("close:{}", wsId);

                    ChannelUtils.close(value, WsCodeEnum.OTHER_LOGIN_OFFLINE);
                }
            }catch (Exception e){
                log.error("e:", e);
            }

        }
    }
}
